분산시스템에서 많이 사용되는 메시지 큐 (message queue)에 대해 공부해봤다. 실무에서 필요한 상황들이 앞으로 더 많이 생길 것 같아서 보편적으로 어떤 상황에서 메시지큐 도입을 고려하고, 여러 구현체들 중 어떤 기술을 써야할지 등을 정리했다.
개념
특정 메시지가 최소 한번은 전달될 수 있도록 보장(보관된 메시지가 꺼낼때까지 안전히 보관된다는 특성, durability)하는 비동기 통신을 지원하는 컴포넌트이다.
기본 아키텍처는 producer(발행자)가 메시지를 생성하여 queue에 저장하고, consumer/subscriber(소비자/구독자)가 queue로부터 메시지를 받아 동작을 수행하는 구조. queue는 여기서 메시지의 버퍼 역할을 한다.
메시지 브로커 라고도 한다.
producer와 consumer는 1:1 이 될 수도 있고, 1:N 관계일 수도 있다.
데이터베이스와의 비교
대부분의 메시지큐는 소비자에게 데이터 전달이 완료되면 자동으로 메시지를 삭제한다. 오랜기간 데이터를 저장하는 용도로는 사용하지 않는다.
queue가 처리하는 메시지들의 양이 작다고 가정한다. 많은 메시지를 큐에 저장할수록 전체 처리량이 저하된다.
장점
비동기
producer는 queue에 메시지를 넣고 응답을 기다리지 않고 다른 일을 할 수 있다.
데이터 유실 방지 (높은 reliablility)
흔히 생산자-소비자 간 통신을 위해 쓰는 HTTP 같은 직접 통신은 메시지 유실 가능성이 있고, 생산자와 소비자가 항상 온라인 상태라고 가정한다. 그러나 둘 중 하나의 서버가 죽는다면 메시지를 잃어버릴 가능성이 생긴다.
그러나 큐를 쓰면 소비자쪽 서버에 장애가 발생하거나 접속이 되지 않는 상태일때도 쉽게 대처할 수 있게 된다.
큐가 소비자에게 전달되지 않은 메시지 혹은 실패한 메시지의 경우 메시지를 삭제하지 않기 대문.
고가용성(HA, High Availability) 유지
장애 복원에 유리 : 시스템 일부에 장애가 발생하더라도 전체적으로 영향을 주지 않는 설계가 가능하다.
확장성
시스템을 구성하는 요소들끼리 독립적으로 동작하게 되어서 추가 서버나 리소스를 시스템에 추가하기 쉽다.
queue방식과 pub-sub(topic)방식
queue
consumer가 메시지를 한번 consume하면 queue에서 삭제된다.
부하에 따라 같은 메시지큐를 listening하는 consumer를 여러개 둘 수 있다.
pub/sub
특정 consumer를 정하지 않고, 어떤 Topic을 구독하는 모든 수신자에게 메시지를 보낸다.
송신자 - 수신자 사이에도 결합이 낮아 높은 확장성을 제공한다.
pub-sub 방식으로 여러 consumer가 메시지를 읽을 때 사용하는 주요 패턴
로드밸런싱(load balancing)
브로커가 consumer 중 하나를 임의로 지정해서 하나의 consumer에게만 메시지를 전달하는 패턴.
메시지 처리 비용이 비싸서 처리를 병렬화 하기 위해 consumer를 추가하고싶을 때 유용하다.
팬 아웃(fan-out)
모든 consumer들에게 메시지가 전달된다. 여러 독립적인 consumer가 서로 간섭 없이 동일한 메시지를 받아 처리할 수 있다.
어떤 상황에서 쓰면 좋을까?
시스템간 느슨한 결합(loose coupling)이 필요한 상황
예를 들어 ‘회원가입‘이나 ‘주문’과 같은 작업은 가입한 시점/주문한 시점에 트리거되어서 유저에게 완료되었다는 안내 메시지를 보내거나 쿠폰을 발급하는 등 여러 동작들을 수행하는 경우가 많다.
메시지 큐를 사용한다면 ‘회원가입’ 이나 ‘주문’을 처리하는 서버가 producer, ‘안내 메시지 전송’, ‘쿠폰 발급’을 처리하는 서버가 consumer가 될 수 있다.
이걸 restAPI에서 동기식으로 모두 처리한다면? 수행속도가 길어지기도 하고, 쿠폰 발급에 실패했는데 가입 자체도 같이 실패하게 되는 등 장애 전파가 발생할 수 있다.
메시지큐를 사용하면 : 메시지를 enqueue하고 응답을 기다리지 않고 다른 동작을 수행할 수 있어서 ‘메시지 전송’이나 ‘쿠폰 발급’에 얼마나 시간이 걸리든 영향을 받지 않을 수 있다. consumer 서버에 장애가 발생하더라도 producer쪽 서버에 영향이 없다. 전달하려는 메시지도 큐에 남아있으므로 consumer 서버가 복구되고나서 재전송할 수 있다.
특정한 시간에만 트래픽이 몰리는 경우 (서비스 특성 or 선착순 이벤트 등등)
많은 트래픽을 발생시키는 부분을 메시지 큐에 넣으면 버퍼역할을 하게 된다.
consumer를 더 추가할 수 있는 구조라면 message queue를 scale out 하고 consumer를 추가해서 처리량을 늘릴 수 있다.